var shareProtocol = require('../protocols/shamir/share.js');
var openProtocol = require('../protocols/shamir/open.js');
var reshareProtocol = require('../protocols/shamir/reshare.js');
var arraysSharing = require('../protocols/arrays/sharing.js');

module.exports = function (jiffClient) {
  /**
   * Share a secret input
   *
   * Can be overriden by extensions to customize behavior
   *
   * @method share
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {number} secret - the number to share (this party's input)
   * @param {number} [threshold=receivers_list.length] - the minimum number of parties needed to reconstruct the secret, defaults to all the receivers
   * @param {Array} [receivers_list=all_parties] - array of party ids to share with, by default, this includes all parties
   * @param {Array} [senders_list=all_parties] - array of party ids to receive from, by default, this includes all parties
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used)
   * @param {string|number} [share_id=auto_gen()] - the tag used to tag the messages sent by this share operation, this tag is used
   *                                   so that parties distinguish messages belonging to this share operation from other
   *                                   share operations between the same parties (when the order of execution is not
   *                                   deterministic). An automatic id is generated by increasing a local counter, default
   *                                   ids suffice when all parties execute all sharing operations with the same senders
   *                                   and receivers in the same order
   * @returns {object} a map (of size equal to the number of sending parties)
   *          where the key is the party id (between 1 and n), or 's1' if 's1' is specified in the senders_list,
   *          and the value is the share object that wraps the value received from that party (the internal value maybe
   *          deferred).
   *
   * @example
   * // share an input value with all parties, and receive all other parties' inputs
   * var shares = jiffClient.share(input);
   * // my party id is '1', so the first share is mine (technically my share of my input value)
   * var my_share = shares[1];
   * // my share of party 2's input
   * var p2_share = shares[2];
   */
  jiffClient.share = function (secret, threshold, receivers_list, senders_list, Zp, share_id) {
    // type check to confirm the secret to be shared is a number
    // for fixed-point extension it should allow non-ints
    if (secret != null && (typeof(secret) !== 'number' || Math.floor(secret) !== secret || secret < 0)) {
      throw new Error('secret must be a non-negative whole number');
    }
    if (secret != null && (secret >= (Zp == null ? jiffClient.Zp : Zp))) {
      throw new Error('secret must fit inside Zp');
    }
    return jiffClient.internal_share(secret, threshold, receivers_list, senders_list, Zp, share_id);
  };

  /**
   * Same as share, but used by internal JIFF primitives/protocols, do not override this!
   * @see {@link module:jiff-client~JIFFClient#share}
   * @method internal_share
   * @instance
   * @memberof module:jiff-client~JIFFClient
   */
  jiffClient.internal_share = shareProtocol.jiff_share.bind(null, jiffClient);

  /**
   * re-share an existing share (value) under a new threshold or to a new set of parties or both.
   * Do not use this to refresh a share (use {@link module:jiff-client~JIFFClient#SecretShare#refresh} instead)
   * @method reshare
   * @instance
   * @memberof module:jiff-client~JIFFClient
   * @param {module:jiff-client~JIFFClient#SecretShare} [share=null] - the share you would like to reshare (null if you are a receiver but not a sender)
   * @param {number} [threshold=receivers_list.length] - the new threshold, defaults to the length of receivers_list param
   * @param {Array} [receivers_list=all_parties] - array of party ids to receive from, by default, this includes all parties
   * @param {Array} [senders_list=all_parties] - array of party ids that posses the share and will reshare it with the receivers, by default, this includes all parties
   * @param {number} [Zp=jiff.Zp] - the Zp of the existing share
   * @param {string} [op_id=auto_gen()] - the operation id which is used to identify this multiplication (and internally, the corresponding beaver triplet).
   *                         This id must be unique, and must be passed by all parties to the same instruction.
   *                         this ensures that every party gets a share from the same triplet for every matching instruction. An automatic id
   *                         is generated by increasing a local counter, default ids suffice when all parties execute the
   *                         instructions in the same order
   * @return {module:jiff-client~JIFFClient#SecretShare} this party's share of the result under the new threshold, or null if this party is not a receiver
   */
  jiffClient.reshare = reshareProtocol.bind(null, jiffClient);

  /**
   * Open a secret share to reconstruct secret.
   * @method open
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {module:jiff-client~JIFFClient#SecretShare} share - this party's share of the secret to reconstruct.
   * @param {Array} [parties=all_parties] - an array with party ids (1 to n) of receiving parties.
   * @param {string|number} [op_id=auto_gen()] - the operation id to be used to tag outgoing messages.
   * @returns {?promise} a (JQuery) promise to the open value of the secret, null if the party is not specified in the parties array as a receiver.
   * @example
   * var shares = jiff_instance.share(input);
   * //multiply the inputs of party 1 and 2 together
   * var result = shares[1].mult(shares[2]);
   * // reveal the result of the multiplication to all parties
   * return jiff_instance.open(result);
   */
  jiffClient.open = openProtocol.jiff_open.bind(null, jiffClient);

  /**
   * Same as open, but used by internal JIFF primitives/protocols, do not override this!
   * @see {@link module:jiff-client~JIFFClient#open}
   * @method internal_open
   * @instance
   * @memberof module:jiff-client~JIFFClient
   */
  jiffClient.internal_open = jiffClient.open;

  /**
   * Receive shares from the specified parties and reconstruct their secret.
   * Use this function in a party that will receive some answer/value but does not have a share of it.
   * @method receive_open
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {Array} senders - an array with party ids (1 to n) specifying the parties sending the shares, this must be provided!
   * @param {Array} [receivers=all_parties] - an array with party ids (1 to n) specifying the parties receiving the result
   * @param {number} [threshold=senders.length] - the min number of parties needed to reconstruct the secret, defaults to all the senders
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used)
   * @param {string|number} [op_id=auto_Gen()] - same as jiff-instance.open
   * @returns {!promise} a (JQuery) promise to the open value of the secret.
   */
  jiffClient.receive_open = function (senders, receivers, threshold, Zp, op_id) {
    if (senders == null) {
      throw new Error('Must provide "senders" parameter in receive_open');
    }

    jiffClient.helpers.sort_ids(senders);
    if (receivers == null) {
      receivers = [];
      for (var i = 1; i <= jiffClient.party_count; i++) {
        receivers.push(i);
      }
    } else {
      jiffClient.helpers.sort_ids(receivers);
    }
    if (Zp == null) {
      Zp = jiffClient.Zp;
    }
    if (threshold == null) {
      threshold = senders.length;
    }

    var imitationSecretShare = new jiffClient.SecretShare({}, senders, threshold, Zp);
    return jiffClient.open(imitationSecretShare, receivers, op_id);
  };

  /**
   * Share an array of values. Each sender may have an array of different length. This is handled by the lengths parameter.
   * This function will reveal the lengths of the shared array.
   *
   * If parties would like to keep the lengths of their arrays secret, they should agree on some public "max" length apriori (either under MPC
   * or as part of the logistics of the computation), all their arrays should be padded to that length by using appropriate default/identity
   * values
   * @method share_array
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {Array} array - the array to be shared.
   * @param {null|number|object} [lengths] - the lengths of the arrays to be shared, has the following options: <br>
   *                                       1. null: lengths are unknown, each sender will publicly reveal the lengths of its own array. <br>
   *                                       2. number: all arrays are of this length <br>
   *                                       3. object: { <sender_party_id>: length }: must specify the length of the array for each sender. <br>
   * @param {number} [threshold=receivers_list.length] - the min number of parties needed to reconstruct the secret, defaults to all the receivers.
   * @param {Array} [receivers_list=all_parties] - array of party ids to share with, by default, this includes all parties.
   * @param {Array} [senders_list=all_parties] - array of party ids to receive from, by default, this includes all parties.
   * @param {number} [Zp=jiff_instance.Zp] - the mod (if null then the default Zp for the instance is used).
   * @param {string|number} [share_id=auto_gen()] - the base tag used to tag the messages sent by this share operation, every element of the array
   *                                   will get a unique id based on the concatenation of base_share_id and the index of the element.
   *                                   This tag is used so that parties distinguish messages belonging to this share operation from
   *                                   other share operations between the same parties (when the order of execution is not
   *                                   deterministic). An automatic id is generated by increasing a local counter, default
   *                                   ids suffice when all parties execute all sharing operations with the same senders
   *                                   and receivers in the same order.
   * @returns {?promise} if the calling party is a receiver then a promise to the shared arrays is returned, the promise will provide an object
   *                    formatted as follows: { <party_id>: [ <1st_share>, <2nd_share>, ..., <(lengths[party_id])th_share> ] }
   *                    where the party_ids are those of the senders.
   *                    if the calling party is not a receiver, then null is returned.
   */
  jiffClient.share_array = arraysSharing.jiff_share_array.bind(null, jiffClient);

  /**
   * Opens an array of secret shares.
   * @method open_array
   * @memberof module:jiff-client~JIFFClient
   * @instance
   * @param {module:jiff-client~JIFFClient#SecretShare[]} shares - an array containing this party's shares of the secrets to reconstruct
   * @param {Array} [parties=all_parties] - an array with party ids (1 to n) of receiving parties
   * @param {string|number} [op_id=auto_gen()] - same as jiff_instance.open
   * @returns {?promise} a (JQuery) promise to ALL the open values of the secret, the promise will yield
   *                    an array of values matching the corresponding given secret share by index. If the calling party
   *                    is not specified in parties, then null is returned
   */
  jiffClient.open_array = arraysSharing.jiff_open_array.bind(null, jiffClient);
};